//#define FORCE_REACH

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using XNA_TD.Menue;
using XNA_TD.Wellen;
using System.Collections.ObjectModel;
using XNA_TD.Shells;
using System.IO;

namespace XNA_TD
{
    public enum Priority
    {
        Kamera,
        Logic,
        Einheit,
        Tower,
        Effekt,
        GUI,
        Input,
        PrioritySize
    }

    public enum Schwierigkeit
    {
        Leicht,
        Normal,
        Schwer
    }

    [Flags]
    public enum Panzerung
    {
        Leicht = 1,
        Keine = 2,
        Schwer = 4
    }

    [Flags]
    public enum GegnerTyp
    {
        Nichts = 0,
        Boden = 1,
        Luft = 2,
        Wasser = 4
    }

    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class GameManager : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        int i = 0;

        public const int TIMESTEPS = 8;
        double time = 0;
        Priority currentPriority;

        List<IUpdate>[] updateList;
        List<IGameUpdate>[] gameUpdateList;
        List<IFixedGameUpdate>[] fixedUpdateList;
        List<IDraw>[] renderList;
        List<IRTT> rttList;
        List<IRunable> newRegisterList;
        List<IRunable> newUnRegisterList;
        int width, height;
        public static bool isHiDef { get; private set; }

        public GameManager()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreparingDeviceSettings += new EventHandler<PreparingDeviceSettingsEventArgs>(graphics_PreparingDeviceSettings);

            Content.RootDirectory = "Content";
            //graphics.PreferredBackBufferWidth = 1920;
            //graphics.PreferredBackBufferHeight = 1080;
            //graphics.IsFullScreen = true;
            graphics.PreferredBackBufferWidth = 800;
            graphics.PreferredBackBufferHeight = 600;
            //IsFixedTimeStep = false;    // zum testen der maximalen geschwindigkeit
            //graphics.SynchronizeWithVerticalRetrace = false;    // zum testen der maximalen geschwindigkeit

            updateList = new List<IUpdate>[(int)Priority.PrioritySize];
            gameUpdateList = new List<IGameUpdate>[(int)Priority.PrioritySize];
            fixedUpdateList = new List<IFixedGameUpdate>[(int)Priority.PrioritySize];
            renderList = new List<IDraw>[(int)Priority.PrioritySize];
            rttList = new List<IRTT>();
            newRegisterList = new List<IRunable>();
            newUnRegisterList = new List<IRunable>();

            for (currentPriority = 0; currentPriority < Priority.PrioritySize; currentPriority++)
            {
                updateList[(int)currentPriority] = new List<IUpdate>();
                gameUpdateList[(int)currentPriority] = new List<IGameUpdate>();
                fixedUpdateList[(int)currentPriority] = new List<IFixedGameUpdate>();
                renderList[(int)currentPriority] = new List<IDraw>();
            }
        }

        ///
        /// start des codeparts von http://communistgames.blogspot.de/2010/11/targeting-reach-and-hidef-pcs-with-same.html
        /// 
        private void graphics_PreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
        {
#if !FORCE_REACH
            for (int i = 0; i < GraphicsAdapter.Adapters.Count; i++)
            {
                if (GraphicsAdapter.Adapters[i].IsProfileSupported(GraphicsProfile.HiDef))
                {
                    e.GraphicsDeviceInformation.Adapter = GraphicsAdapter.Adapters[i];
                    e.GraphicsDeviceInformation.GraphicsProfile = GraphicsProfile.HiDef;
                    isHiDef = true;
                    return;
                }
            }
#endif

            for (int i = 0; i < GraphicsAdapter.Adapters.Count; i++)
            {
                if (GraphicsAdapter.Adapters[i].IsProfileSupported(GraphicsProfile.Reach))
                {
                    e.GraphicsDeviceInformation.Adapter = GraphicsAdapter.Adapters[i];
                    e.GraphicsDeviceInformation.GraphicsProfile = GraphicsProfile.Reach;
                    isHiDef = false;
                    return;
                }
            }
        }
        ///
        /// ende des codeparts
        /// 

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            IRunable.graphics = graphics;
            IRunable.spriteBatch = spriteBatch;
            IRunable.Content = Content;
            IRunable.gameManager = this;
            IRunable.shader = Content.Load<Effect>("Shader");
            IsMouseVisible = true;
            height = graphics.GraphicsDevice.Viewport.Height;
            width = graphics.GraphicsDevice.Viewport.Width;

            new ButtonPressed();
            new Map();
            Register(new Wasser());
            Register(new RenderShadowMap());
            Register(new Base());
            Register(new Kamera());
            Register(new FPS());
            Register(new UnitHealth());
            new SchwierigkeitsMenue();
            //new ShadowMapTest();
            BauMenue.Initialize();
            //new TimeTest();

            // TODO: use this.Content to load your game content here
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();
            if (Keyboard.GetState().IsKeyDown(Keys.Escape))
                this.Exit();
            IRunable.gameTime = gameTime;

            if (width  != graphics.GraphicsDevice.Viewport.Width ||
                height != graphics.GraphicsDevice.Viewport.Height)
            {
                width = graphics.GraphicsDevice.Viewport.Width;
                height = graphics.GraphicsDevice.Viewport.Height;
                foreach (var update in fixedUpdateList[(int)Priority.GUI])
                {
                    GUI gui = update as GUI;
                    if (gui != null)
                    {
                        gui.Resize();
                    }
                }
            }

            // TODO: Add your update logic here

            if (IRunable.gameSpeed != 0)
            {
                int speed = TIMESTEPS / IRunable.gameSpeed;
                while (time < gameTime.TotalGameTime.TotalMilliseconds)
                {
                    time += speed;
                    IRunable.inGameTime += TIMESTEPS;
                    for (currentPriority = 0; currentPriority < Priority.PrioritySize; currentPriority++)
                    {
                        foreach (var update in fixedUpdateList[(int)currentPriority])
                        {
                            update.FixedGameUpdate();
                        }
                    }

                    Register();
                    UnRegister();
                }
            }
            else
            {
                time = gameTime.TotalGameTime.TotalMilliseconds;
            }


            for (currentPriority = 0; currentPriority < Priority.PrioritySize; currentPriority++)
            {
                if (IRunable.gameSpeed > 0)
                {
                    foreach (var update in gameUpdateList[(int)currentPriority])
                    {
                        update.GameUpdate();
                    }
                }
                foreach (var update in updateList[(int)currentPriority])
                {
                    update.Update();
                }
            }

            Register();
            UnRegister();

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            foreach (var rtt in rttList)
            {
                rtt.RTT();
            }

            GraphicsDevice.SetRenderTarget(null);
            GraphicsDevice.Clear(Color.CornflowerBlue);

            for (currentPriority = 0; currentPriority < Priority.PrioritySize; currentPriority++)
            {
                foreach (var draw in renderList[(int)currentPriority])
                {
                    draw.Draw();
                }
            }

            base.Draw(gameTime);

            if (ButtonPressed.buttonPressed.KeyPressed(Keys.F10))
            {
                var bild = new Texture2D(graphics.GraphicsDevice, graphics.GraphicsDevice.Viewport.Width,graphics.GraphicsDevice.Viewport.Height);

                Color[] colors = new Color[bild.Width*bild.Height];
                
                graphics.GraphicsDevice.GetBackBufferData(colors);
                bild.SetData(colors);
                using(FileStream fs = new FileStream(@"screenshot"+(i++)+@".png", FileMode.OpenOrCreate))
                {
                    bild.SaveAsPng(fs,bild.Width,bild.Height);
                }

            }
        }

        private void Register()
        {
            foreach (var runable in newRegisterList)
            {
                InsertIntoList(runable);
            }
            newRegisterList.Clear();

            int updateCount = 0;
            int gameUpdateCount = 0;
            int fixedGameUpdateCount = 0;
            int renderCount = 0;
            for (currentPriority = 0; currentPriority < Priority.PrioritySize; currentPriority++)
            {
                updateCount += updateList[(int)currentPriority].Count;
                gameUpdateCount += gameUpdateList[(int)currentPriority].Count;
                fixedGameUpdateCount += fixedUpdateList[(int)currentPriority].Count;
                renderCount += renderList[(int)currentPriority].Count;
            }
            DebugInfo.Instance.add("UpdatesList", updateCount);
            DebugInfo.Instance.add("GameUpdatesList", gameUpdateCount);
            DebugInfo.Instance.add("FixedGameUpdateList", fixedGameUpdateCount);
            DebugInfo.Instance.add("RenderList", renderCount);
            DebugInfo.Instance.add("RTTList", rttList.Count);
        }

        private void UnRegister()
        {
            foreach (var runable in newUnRegisterList)
            {
                DeleteFromList(runable);
            }
            newUnRegisterList.Clear();
        }

        private void InsertIntoList(IRunable runable)
        {
            if (runable as IUpdate != null)
            {
                updateList[(int)runable.priority].Add(runable as IUpdate);
            }
            if (runable as IGameUpdate != null)
            {
                gameUpdateList[(int)runable.priority].Add(runable as IGameUpdate);
            }
            if (runable as IFixedGameUpdate != null)
            {
                fixedUpdateList[(int)runable.priority].Add(runable as IFixedGameUpdate);
            }
            if (runable as IDraw != null)
            {
                renderList[(int)runable.priority].Add(runable as IDraw);
            }
            if (runable as IRTT != null)
            {
                rttList.Add(runable as IRTT);
            }
            runable.Load();
        }

        private void DeleteFromList(IRunable runable)
        {
            if (runable as IUpdate != null)
            {
                updateList[(int)runable.priority].Remove(runable as IUpdate);
            }
            if (runable as IGameUpdate != null)
            {
                gameUpdateList[(int)runable.priority].Remove(runable as IGameUpdate);
            }
            if (runable as IFixedGameUpdate != null)
            {
                fixedUpdateList[(int)runable.priority].Remove(runable as IFixedGameUpdate);
            }
            if (runable as IDraw != null)
            {
                renderList[(int)runable.priority].Remove(runable as IDraw);
            }
            if (runable as IRTT != null)
            {
                rttList.Remove(runable as IRTT);
            }
            runable.UnLoad();
        }

        public void Register(IRunable runable)
        {
            if (runable.priority == currentPriority)
                newRegisterList.Add(runable);
            else
                InsertIntoList(runable);
        }

        public void UnRegister(IRunable runable)
        {
            if (runable.priority == currentPriority)
                newUnRegisterList.Add(runable);
            else
                DeleteFromList(runable);
        }

        public bool durchTurmGeblockt(Vector2 position)
        {
            foreach (var tower in renderList[(int)Priority.Tower])
            {
                if ((int)position.X == (int)((GameObject)tower).position.X &&
                    (int)position.Y == (int)((GameObject)tower).position.Z)
                {
                    return true;
                }
            }
            return false;
        }
        
        public ReadOnlyCollection<IDraw> getRenderList(Priority priority)
        {
            return renderList[(int)priority].AsReadOnly();
        }
    }
}
